const http = require("http");
const Emitter = require("events");
const context = require("./context");
const request = require("./request");
const response = require("./response");

// 1. 构建Application实例,koa入口
// 2. 创建一个http服务, 成功时回调
// 3. 回调函数是通过use注册传入的函数
// 4. 将context、request、response挂载到Application实例上
// 5. 使用koa时,会有ctx对象,自己构造 ctx 对象
// 6. 将 ctx 对象传递给回调函数

/**
 *
 */
class Application extends Emitter {
  constructor() {
    super();
    this.callbackFunc;
    this.middlewares = [];
    // 每次都会构建新的context、request、response;
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
  }

  listen(...args) {
    let server = http.createServer(this.callback());
    server.listen(...args);
  }

  // 收集中间件
  use(fn) {
    // this.callbackFunc = fn;
    this.middlewares.push(fn);
  }

  callback() {
    // 框架错误处理
    if (!this.listenerCount("error")) this.on("error", this.onerror);
    return (req, res) => {
      let ctx = this.createContext(req, res);
      // this.callbackFunc(ctx);
      let respond = () => this.responseBody(ctx);
      // 中间层处理错误
      let onerror = (err) => ctx.onerror(err);
      // 先处理中间件,然后去响应
      let fn = this.compose();

      return fn(ctx).then(respond).catch(onerror);
    };
  }

  /**
   * 用于构造 ctx 对象
   * @param {Object} req Node原生的req实例
   * @param {Object} res Node原生的res实例
   */
  createContext(req, res) {
    // 针对每一个请求
    let ctx = Object.create(this.context);
    ctx.request = Object.create(this.request);
    ctx.response = Object.create(this.response);

    ctx.app = ctx.request.app = ctx.response.app = this;
    ctx.req = ctx.request.req = req;
    ctx.res = ctx.response.res = res;

    return ctx;
  }

  // 简单实现洋葱模型
  compose() {
    return async (ctx) => {
      let len = this.middlewares.length;
      let next = async () => Promise.resolve();

      for (let i = len - 1; i >= 0; i--) {
        let curentMiddleware = this.middlewares[i];
        next = createNext(curentMiddleware, next);
      }
      await next();

      function createNext(middlewares, oldNext) {
        return async () => await middlewares(ctx, oldNext);
      }
    };
  }

  /**
   * 处理响应请求
   * @param {Object} ctx
   */
  responseBody(ctx) {
    let content = ctx.body;
    if (typeof content === "string") {
      ctx.res.end(content);
    } else {
      ctx.res.end(JSON.stringify(content));
    }
  }

  onerror(err) {
    let msg = err.stack || err.toString();
    console.error(msg.replace(/^/g, " "));
  }
}

module.exports = Application;

